#include "drunkscenemodel.h"
#include "sceneobject.h"
#include <qmath.h>
#include <cstdlib>
#include <QTimer>
#include <QTime>
#include <QDebug>
#include "noise.h"

DrunkSceneModel::DrunkSceneModel(int updateStateIntervals,float mapWidth,float mapHeight,Noise *noise,float movementSpeedInMps,int fallingDownPenalty,QObject *parent) :
    QObject(parent) {

    this->noise=noise;
    this->mapWidth = mapWidth;
    this->mapLength = mapHeight;
    this->movementSpeed = movementSpeedInMps;
    this->startingTime=new QTime;

    this->tilt = 0.0;
    this->inputAcc = 0.0;
    this->tiltFactor = 1.0;

    this->yPosition = 0;
    this->xPosition = 2.5;
    this->isStanding = true;

    this->fallingDownPenalty = fallingDownPenalty;
    this->timeToStandInMilliseconds = fallingDownPenalty;

    this->updateStateIntervals=updateStateIntervals;
    this->gravityEffect=1.0;
    this->randomness=1.0;
    this->speedBoost=1.0;

    totalMsToComplete = -1;
}

DrunkSceneModel::~DrunkSceneModel(){
    if(this->startingTime!=0)
        delete this->startingTime;
}

QLinkedList<SceneObject* > DrunkSceneModel::getObjectsAtDistance(double distance) {
        QLinkedList<SceneObject* >::Iterator iterator=inactiveObjects.begin();
        QLinkedList<SceneObject* > objectsAtDistance;

        while( iterator!=inactiveObjects.end() && ((*iterator)->getYPosition()-yPosition)<= distance ) {
            objectsAtDistance<< (*iterator);
            ++iterator;
        }

        return objectsAtDistance;
}

float DrunkSceneModel::getYPosition() {
        return this->yPosition;
}

float DrunkSceneModel::getXPosition() {
        return this->xPosition;
}

float DrunkSceneModel::getTilt() {
        return this->tilt;
}

float DrunkSceneModel::getInputAcc() {
        return this->inputAcc;
}

float DrunkSceneModel::getOpponentsPosition() {
        return this->opponentPos;
}

float DrunkSceneModel::getMapLength() {
        return this->mapLength;
}

float DrunkSceneModel::getMapWidth() {
        return this->mapWidth;
}

bool DrunkSceneModel::getIsStanding() {
        return this->isStanding;
}

void DrunkSceneModel::interactWithObject(SceneObject *obj, bool activate) {
        if (activate) {
            if(obj->getGravityFactor()==1 && obj->getRandomnessFactor()==1 && obj->getWalkingSpeedFactor()==1){
                if(tilt<0.0)
                    tilt=-100;
                else
                    tilt=100;
            }
            else{
                gravityEffect*=obj->getGravityFactor();
                randomness*=obj->getRandomnessFactor();
                speedBoost*=obj->getWalkingSpeedFactor();
            }
        }
        else {
            gravityEffect/=obj->getGravityFactor();
            randomness/=obj->getRandomnessFactor();
            speedBoost/=obj->getWalkingSpeedFactor();
        }
}

void DrunkSceneModel::updateState() {
    if(mapLength<=yPosition)
        return;

    if(startingTime->isNull())
        *startingTime=QTime::currentTime();

    if(tilt < -90.0 || tilt > 90.0)
        isStanding=false;

    if(isStanding){
        //tilt control
        moveHeadAndXPosition();
        //move up to the map
        yPosition += (updateStateIntervals / 1000.0) * movementSpeed * speedBoost;
        if(yPosition>=mapLength){
            yPosition=mapLength;
            tilt=0.0;
            xPosition=mapWidth/2;
            totalMsToComplete = startingTime->msecsTo(QTime::currentTime());
            emit reachedEndOfMap(totalMsToComplete);
        }

        //collision detection
        collisionDetection();

        //remove expired effects
        removeEffects();

        }
    else {
        if (timeToStandInMilliseconds > 0){
            timeToStandInMilliseconds -= updateStateIntervals;
            emit standCountdown(timeToStandInMilliseconds);
        }
        else{
            tilt = 0.0;
            xPosition = mapWidth / 2.0;
            isStanding = true;
            timeToStandInMilliseconds = fallingDownPenalty;
        }
    }
    emit playerPosition(yPosition);
}

void DrunkSceneModel::setNavigationDifficulty(float navigationDifficulty) {
        tiltFactor = navigationDifficulty;
}

void DrunkSceneModel::putObject(SceneObject* object) {
    if(inactiveObjects.isEmpty()){
        inactiveObjects.append(object);
    }
    else{
        QLinkedList<SceneObject *>::Iterator iterator=inactiveObjects.begin();
        while(iterator!=inactiveObjects.end()){
            if(object->getYPosition()<(*iterator)->getYPosition())
                break;
            else
                ++iterator;
        }
        inactiveObjects.insert(iterator,object);
    }
}

void DrunkSceneModel::setInput(float acc) {
    if(acc==inputAcc)
        return;

    if (acc < -1.0)
        inputAcc = -1.0;
    else if (acc > 1.0)
        inputAcc = 1.0;
    else
        inputAcc = acc;
}

void DrunkSceneModel::setOpponentPosition(float yPos) {
    opponentPos = yPos;
}

void DrunkSceneModel::moveHeadAndXPosition(){
    // If the character has not fallen, apply three different accelerations
    // Acceleration #1 - Gravity
    tilt += tilt * 0.2 * tiltFactor * gravityEffect;

    // Acceleration #2 - Random variation
    tilt += noise->getMultiSineAt(yPosition, 9.3123, 5, 0.571282, 0.79) * 8.0 * tiltFactor * randomness;

    // Acceleration #3 - User input
    if (inputAcc > -1.0 && inputAcc < 1.0) {
            float tiltChange = inputAcc;
            tiltChange = tiltChange * 16.0;
            tilt += tiltChange;
    }
    //X-axis movement
    if (tilt >= 10.0) {
            float move = tilt / 10.0;//a number from
            move *= 0.03;//reduce impact of movement
            if ((xPosition + move) > 5)
                    xPosition = 5;
            else
                    xPosition += move;
    } else if (tilt < -10.0) {
            float move = -tilt / 10.0;
            move *= 0.03;//reduce impact of movement
            if ((xPosition - move) < 0)
                    xPosition = 0;
            else
                    xPosition -= move;
    }
}

void DrunkSceneModel::collisionDetection(){
    QLinkedList<SceneObject *>::Iterator collisionIterator=inactiveObjects.begin();

    while(collisionIterator!=inactiveObjects.end()){
        //objects are sorted by distance so in case we find one that is further than 10cm, the next ones are too
        if( ((*collisionIterator)->getYPosition()-yPosition)>0.10)
            break;

        //this one collides with the character in the X Axis
        if(abs((*collisionIterator)->getXPosition() - xPosition) < (*collisionIterator)->getWidth() / 2.0){
            interactWithObject(*collisionIterator,true);
            (*collisionIterator)->activate();
            //insert the object in the activeObjects list sorted by effect expiration time ascending
            if(activeObjects.isEmpty()){
                activeObjects.append(*collisionIterator);
            }
            else{
                QLinkedList<SceneObject *>::Iterator inputIterator=activeObjects.begin();
                    while(inputIterator!=activeObjects.end()){

                        if( *((*collisionIterator)->getExpiresAt()) < *((*inputIterator)->getExpiresAt()) )
                            break;
                        else
                            ++inputIterator;
                    }
                    activeObjects.insert(inputIterator,*collisionIterator);
                }
            //remove it from the current list
            collisionIterator=inactiveObjects.erase(collisionIterator);
        }
        else{//no collision detected,remove this object and move to the next object check
            collisionIterator=inactiveObjects.erase(collisionIterator);
        }
    }
}

void DrunkSceneModel::removeEffects(){
    QLinkedList<SceneObject *>::Iterator activatedObjectsIterator=activeObjects.begin();

    while(activatedObjectsIterator!=activeObjects.end()){
        //the object whose effect is to expire sooner than the others has not expired yet
        if(QTime::currentTime()< *((*activatedObjectsIterator)->getExpiresAt()))
            break;

        interactWithObject(*activatedObjectsIterator,false);

        SceneObject *object=*activatedObjectsIterator;
        activatedObjectsIterator=activeObjects.erase(activatedObjectsIterator);

        delete object;
    }
}
